Skip to content

feat: lightweight static render for non-animated mobile charts (+ ReferenceLine strokeWidth) - #840

Merged
hcopp merged 3 commits into
coinbase:masterfrom
alfonsocj:mux/add-sparkline-lightweight-mode
Aug 11, 2026
Merged

hcopp merged 3 commits into
coinbase:masterfrom
alfonsocj:mux/add-sparkline-lightweight-mode

Conversation

@alfonsocj

@alfonsocj alfonsocj commented Aug 11, 2026 •

Copy link
Copy Markdown
Contributor

What changed? Why?

Makes the CDS cds-mobile chart render a lightweight, static line when it isn't animating — with no new public API. The win rides on the existing animate prop plus an internal ScrubberProvider optimization:

  • Path static branch (the clip win). Path now delegates to a reanimated-free StaticChartPath when the chart isn't animating (animate={false}, which is also what non-interactive charts want). It clips with an axis-aligned rect (routed to canvas.clipRect, a GPU scissor) instead of the anti-aliased path clip (Skia.Path.addRect() → canvas.clipPath) that falls back to Skia's CPU SoftwarePathRenderer. The animated branch is unchanged.
  • ScrubberProvider (the scrubber win). When enableScrubbing is false (the default), the provider now skips the pan gesture and the useAnimatedReaction allocation entirely (split into enabled/disabled providers). Every non-scrubbing chart benefits, not just a special mode.
  • ReferenceLine strokeWidth. New prop so callers can size the reference line (previously fixed at the line component's default).

Why: non-interactive row sparklines in scrolling lists pay a main-thread Skia cost from the anti-aliased path clip when many rows recycle. consumer/react-native #73650 worked around it with a bespoke LightweightSparkline; per the reviewer's comment the clean long-term fix is a static mode on the CDS chart so callers get the win without a bespoke component. This delivers that via existing props.

Note (review outcome): an earlier revision added an interactive prop. Per review, that was dropped — scrubbing is already opt-in via enableScrubbing and animation via animate, so the lightweight render is reached with animate={false} and the ScrubberProvider efficiency improvement, with no new API surface.

Consumer migration (follow-up PR, after release): replace <LightweightSparkline …/> with <LineChart animate={false} …> + <ReferenceLine dataY={…} />, then delete the bespoke component.

Linear: https://linear.app/coinbase/issue/APP-770

Root cause (required for bugfixes)

N/A — performance feature, not a bugfix. (The cost is the anti-aliased path clip → SoftwarePathRenderer; a rect clip is a GPU scissor and preserves bounds, so curve="bump" overshoot cannot bleed out.)

UI changes

No visible change to animated (default) usage — the animated render path is untouched. animate={false} renders the line identically at rest (the rect clip preserves the same bounds as the previous path clip); it only drops the entrance-reveal animation. A new "Lightweight (Static)" LineChart story variant was added (data crossing a dotted ReferenceLine) so the mobile visreg target (apps/expo-app) captures it.

iOS Old iOS New
no change to animated usage ios-simulator
Android Old Android New
no change to animated usage android-emulator

Captured on the iOS simulator and Android emulator running the new "Lightweight (Static)" LineChart story (animate={false}); visual parity is also covered by the story + visreg.

Testing

How has it been tested?

  • Unit tests
  • Interaction tests
  • Pseudo State tests
  • Manual - Web
  • Manual - Android (Emulator / Device)
  • Manual - iOS (Emulator / Device)

Testing instructions

  • chart/__tests__/Path.test.tsx: asserts the static branch clips with a rect (canvas.clipRect) not an SkPath, honors an explicit clipPath, and that the animated path is still used when the chart animates.
  • chart/scrubber/__tests__/ScrubberProvider.test.tsx: asserts the pan gesture + useAnimatedReaction are set up when enableScrubbing is true and skipped when it's false.
  • chart/line/__tests__/LineChart.test.tsx: smoke-renders an animate={false} chart.
  • Full mobile chart suite green (356 tests), typecheck, lint (0 errors), format all pass.
  • Guards flip red if their part of the diff is reverted.

Residual / follow-up (not addressed here): in the static case useChartLayout's rAF measure→setState double render and the full scale/domain recompute still run, so this is lighter than the bare LightweightSparkline but not equivalent. consumer/react-native should confirm with a native trace before removing its kill switch.

Illustrations/Icons Checklist

N/A — no changes under packages/illustrations/** or packages/icons/**.

Change management

type=routine
risk=low
impact=sev5

automerge=false

@cb-heimdall

cb-heimdall commented Aug 11, 2026 •

Copy link
Copy Markdown
Collaborator

✅ Heimdall Review Status

Requirement Status More Info
Reviews ✅ 1/1
Denominator calculation
Show calculation
1 if user is bot 0
1 if user is external 0
2 if repo is sensitive 0
From .codeflow.yml 1
Additional review requirements
Show calculation
Max 0
0
From CODEOWNERS 1
Global minimum 0
Max 1
1
1 if commit is unverified 1
Sum 2
CODEOWNERS ✅ See below

✅ CODEOWNERS

Code Owner Status Calculation
ui-systems-eng-team ✅ 1/1
Denominator calculation
Additional CODEOWNERS Requirement
Show calculation
Sum 0
0
From CODEOWNERS 1
Sum 1

@alfonsocj
alfonsocj marked this pull request as ready for review August 11, 2026 16:11
Comment thread packages/mobile/src/visualizations/chart/CartesianChart.tsx Outdated
Comment thread packages/mobile/src/visualizations/chart/Path.tsx Outdated
Comment thread packages/mobile/src/visualizations/chart/Path.tsx Outdated
@alfonsocj alfonsocj changed the title feat: add interactive prop for lightweight static mode to mobile charts feat: lightweight static render for non-animated mobile charts (+ ReferenceLine strokeWidth) Aug 11, 2026
@alfonsocj
alfonsocj requested a review from hcopp August 11, 2026 17:03
Comment thread packages/mobile/src/visualizations/chart/scrubber/ScrubberProvider.tsx Outdated
Comment thread packages/mobile/src/visualizations/chart/CartesianChart.tsx Outdated
Comment thread packages/mobile/src/visualizations/chart/Path.tsx
@alfonsocj
alfonsocj force-pushed the mux/add-sparkline-lightweight-mode branch from bdc04fe to ee78184 Compare August 11, 2026 17:42
alfonsocj and others added 3 commits August 11, 2026 13:51
Adds `interactive?: boolean` (default true) to cds-mobile CartesianChart
(inherited by LineChart). `interactive={false}` skips the scrubber context and
entrance animation and renders lines via a reanimated-free static Path that
clips with a cheap rect (canvas.clipRect) instead of the anti-aliased path clip,
cutting the main-thread SoftwarePathRenderer cost when many charts recycle in
scrolling lists. Additive and backward-compatible (default preserves behavior).

Linear: APP-770

Co-Authored-By: Claude <noreply@anthropic.com>
…rubberProvider

- Remove the `interactive` prop; the lightweight static render now rides on the existing
  `animate={false}` (Path routes to a reanimated-free StaticChartPath with a cheap rect clip).
- ScrubberProvider skips the pan gesture + animated reaction when scrubbing is disabled.
- Add a `strokeWidth` prop to ReferenceLine; narrow StaticChartPath props (review nit).
- Update docs, story (animate={false}), and tests; add ScrubberProvider test.
- Bump @coinbase/cds-mobile to 9.13.0 (+ release sync).

Linear: APP-770

Co-Authored-By: Claude <noreply@anthropic.com>
- ScrubberProvider: enabled provider no longer branches on enableScrubbing (always on).
- Path: static path uses useDerivedValue so an animated d (e.g. reference lines) tracks scrubbing.
- Trim verbose JSDoc/comments per review.

Co-Authored-By: Claude <noreply@anthropic.com>
@alfonsocj
alfonsocj force-pushed the mux/add-sparkline-lightweight-mode branch from ee78184 to 1c2c9e4 Compare August 11, 2026 17:51
@hcopp
hcopp merged commit d9cb927 into coinbase:master Aug 11, 2026
29 of 31 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Development

Successfully merging this pull request may close these issues.

3 participants